package cn.edu.sdju.fengguoyu.ahoedgemonitor;

import cn.edu.sdju.fengguoyu.po.ServiceInfoTableRow;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.util.Pair;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Random;
import java.util.ResourceBundle;

public class CanvasMap implements Initializable {


    static boolean runOnce = true;
    static Image serverIcon = null;
    public CanvasMap() {
        if (serverIcon == null) {
            try {
                serverIcon = new Image(new FileInputStream(new File(App.class.getResource("server_icon.png").getFile())));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
    SnapshotParameters parameters;
    boolean isDrag = false;
    public double baseX = 0, baseY = 0;
    final int framesPerSecond = 60;
    private static Canvas drawCanvas = null;
    @FXML
    public Canvas dataMap;
    double startX, startY, diffX, diffY, orginX, orginY;
    int gridSize = 40;
    int hitIdx = -1;
    boolean isMoving = false;
    int idx = -1;
    Point2D mousePoint;
    long lastTimeStamp;


    double nodeHitWidth = 170;
    ArrayList<String> nodeHitStrings = new ArrayList<>(5);
    public void drawNodeHit(GraphicsContext gc) {
        if (idx < 0 || hitIdx >= 0) return;
        double maxWith = -1;
        double height = 0;
        if (App.getApp().sprites.get(idx) instanceof UserSprite) {
            // TODO
            UserInfoTableRow userInfoTableRow = App.getApp().sprite2UserInfoTable.get(App.getApp().sprites.get(idx));
            nodeHitStrings.clear();
            nodeHitStrings.add("Address: " + userInfoTableRow.getUserAddress());
            nodeHitStrings.add("ID: " + userInfoTableRow.getUserId());
        } else {
            ServiceInfoTableRow serviceInfo = App.getApp().sprite2ServiceInfoTable.get(App.getApp().sprites.get(idx));

            nodeHitStrings.clear();
            nodeHitStrings.add("Address: " + serviceInfo.getServiceAddress());
            nodeHitStrings.add("Rank: " + serviceInfo.getRank());
        }
        for (String line: nodeHitStrings
             ) {
            Text text = new Text(line);
            text.setFont(new Font("Arial", 14));
            Bounds bounds = text.getBoundsInLocal();
            double textWidth = bounds.getWidth();
            maxWith = Math.max(maxWith, textWidth);
            height = bounds.getHeight();
        }
        double nodeHitHeight = nodeHitStrings.size()*height + 8;
        gc.setFill(Color.color(0,0,0,.4));
        gc.fillRoundRect(mousePoint.getX(),mousePoint.getY()-nodeHitHeight,maxWith+16,nodeHitHeight,10,10);
        gc.setFont(new Font("Arial", 14));
        gc.setFill(Color.WHITE);
        nodeHitHeight -= 2;
        for (String line: nodeHitStrings
        ) {
            nodeHitHeight -= height;
            gc.fillText(line,mousePoint.getX()+8, mousePoint.getY()-nodeHitHeight);
        }
    }

    public void drawConnection(GraphicsContext gc) {
        gc.setFill(Color.BLACK);
        gc.setLineWidth(2);
        for (Pair<Sprite, Sprite> pair: App.getApp().connections
             ) {
            Sprite x = pair.getKey();
            Sprite y = pair.getValue();
            gc.strokeLine(x.x+(x.width/2), x.y+(x.height/2), y.x+(y.width/2), y.y+(y.height/2));
        }
    }
    private void initBackGround (GraphicsContext gc) {
        gc.setFill(Color.WHITE);
        gc.fillRect(0, 0, drawCanvas.getWidth(), drawCanvas.getHeight());
        gc.setStroke(Color.DARKGREY);
        gc.setLineWidth(.3);
        // 绘制横向的大格线
        for (int i = 0; i <= 1000; i += gridSize) {
            gc.strokeLine(0, i, 1000, i);
        }
        // 绘制纵向的大格线
        for (int j = 0; j <= 1000; j += gridSize) {
            gc.strokeLine(j, 0, j, 1000);
        }

    }

    public void renderView() {
        GraphicsContext visualGc = dataMap.getGraphicsContext2D();
        parameters.setViewport(new Rectangle2D(Math.max(baseX, 0.0), Math.max(baseY, 0.0),dataMap.getWidth(), dataMap.getHeight()));
        visualGc.drawImage(drawCanvas.snapshot(parameters, null),0,0, dataMap.getWidth(), dataMap.getHeight(), 0, 0, dataMap.getWidth(), dataMap.getHeight());
        visualGc.setFill(Color.GRAY);
        visualGc.setLineWidth(.3);
        visualGc.strokeLine(1, 1, 1, dataMap.getHeight());
        if (System.currentTimeMillis() - lastTimeStamp > 1000) {
            drawNodeHit(visualGc);
        }
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        if (drawCanvas == null) {
            drawCanvas  = new Canvas();
            drawCanvas.setHeight(1000);
            drawCanvas.setWidth(1000);
        }
        parameters = new SnapshotParameters();
        nodeHitStrings.add("IP地址：localhost:23332333233323332333");
        nodeHitStrings.add("Rank:1");
        GraphicsContext gc =  drawCanvas.getGraphicsContext2D();
        ArrayList<Sprite> sprites = App.getApp().sprites;
        App app = App.getApp();
        Random random = new Random();
        if (runOnce) {
            runOnce = false;
            for (ServiceInfoTableRow serviceTable: app.serviceInfoTableRowObservableList) {
                ServerSprite sprite = new ServerSprite(random.nextInt(101), random.nextInt(101));
                sprite.setLabel(String.format("%s[%d]",serviceTable.getNamed(), serviceTable.getId()));
                sprites.add(sprite);
            }
        }
        app.serviceInfoTableRowObservableList.addListener(new ListChangeListener<ServiceInfoTableRow>() {
            @Override
            public void onChanged(Change<? extends ServiceInfoTableRow> c) {
                while (c.next()) {
                    if (c.wasAdded()) {
                        for (ServiceInfoTableRow row: c.getAddedSubList()) {
                            if (row.getType().equals("REG")) {
                                RegisterServerSprite sprite = new RegisterServerSprite(120,120);
                                row.setSprite(sprite);
                                App.getApp().sprite2ServiceInfoTable.put(sprite, row);
                                sprites.add(sprite);

                            } else {
                                ServerSprite sprite = new ServerSprite(random.nextInt(101), random.nextInt(101));
                                row.setSprite(sprite);
                                sprite.setLabel(String.format("%s[%d]",row.getNamed(), row.getId()));
                                sprites.add(sprite);
                                App.getApp().sprite2ServiceInfoTable.put(sprite, row);
                                App.getApp().connections.add(new Pair<>(sprites.get(0), sprite));
                            }

                        }
                    }
                }
            }
        });
        app.userInfoTableRowsObservableList.addListener(new ListChangeListener<UserInfoTableRow>() {
            @Override
            public void onChanged(Change<? extends UserInfoTableRow> c) {
                while (c.next()) {
                    if (c.wasAdded()) {
                        for (UserInfoTableRow row: c.getAddedSubList()) {
                            UserSprite sprite = new UserSprite(random.nextInt(200), random.nextInt(200));
                            sprite.label = String.format("User[%s]", row.getUserId());
                            row.userSprite = sprite;
                            sprites.add(sprite);
                            App.getApp().sprite2UserInfoTable.put(sprite, row);
                        }
                    }
                }
            }
        });
        dataMap.setOnMouseMoved((MouseEvent e) -> {
            startX = e.getX() + baseX;
            startY = e.getY() + baseY;
            for (int i = 0; i < sprites.size(); i++) {
                if ((sprites.get(i).getX() < startX && startX <= sprites.get(i).getX() + sprites.get(i).getWidth())
                        && (sprites.get(i).getY() < startY && startY <= sprites.get(i).getY() + sprites.get(i).getHeight())) {
                    if (idx < 0) {
                        lastTimeStamp = System.currentTimeMillis();
                        dataMap.setCursor(Cursor.HAND);
                    }
                    idx = i;
                    mousePoint = new Point2D(e.getX(), e.getY());

                    return;
                }
            }
            if (idx >= 0) {
                dataMap.setCursor(Cursor.DEFAULT);
            }
            idx = -1;
        });

        dataMap.setOnMousePressed((MouseEvent e) -> {
            orginX = e.getX();
            orginY = e.getY();
            startX = e.getX() + baseX;
            startY = e.getY() + baseY;
            boolean isHit = false;
            if (e.isMiddleButtonDown()) {
                isMoving = true;
                dataMap.setCursor(Cursor.MOVE);
            } else {
                for (int i = 0; i < sprites.size(); i++) {
                    if ((sprites.get(i).getX() < startX && startX <= sprites.get(i).getX() + sprites.get(i).getWidth())
                            && (sprites.get(i).getY() < startY && startY <= sprites.get(i).getY() + sprites.get(i).getHeight())) {
                        diffX = startX - sprites.get(i).getX();
                        diffY = startY - sprites.get(i).getY();
                        hitIdx = i;
                        isHit = true;
                        break;
                    }
                }
                if (!isHit) {
                    hitIdx = -1;
                }
            }

        });
        dataMap.setOnMouseReleased((MouseEvent e) -> {
            mousePoint = new Point2D(e.getX(), e.getY());
            lastTimeStamp = System.currentTimeMillis();
            if (isMoving) {
                isMoving = false;
                dataMap.setCursor(Cursor.DEFAULT);
            } else {
                if (hitIdx >=  0 && !isDrag) {
                    System.out.println("Test Clicked");
                    Stage newStage = new Stage();
                    Scene newScene = null;
                    try {
                        newScene = new Scene((Parent) PrimaryController.loadLayoutWithoutCached("status_info"));
                        newScene.getStylesheets().add(App.class.getResource("css/main.css").toExternalForm());
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                    newStage.setResizable(false);
                    newStage.setMaximized(false);
                    newStage.getIcons().add(serverIcon);
                    newStage.setScene(newScene);
                    newStage.setTitle("边缘计算节点详情");
                    newStage.show();
                }
                isDrag = false;
            }
            hitIdx = -1;
        });
        dataMap.setOnMouseDragged((MouseEvent e) -> {
            if (e.isMiddleButtonDown() && isMoving) {
                double deltaX = e.getX() - orginX;
                double deltaY = e.getY() - orginY;
                baseX = Math.max(0.0, baseX-deltaX);
                baseY = Math.max(0.0, baseY-deltaY);
                baseX = Math.min(342.0, baseX-deltaX);
                baseY = Math.min(600, baseY-deltaY);
                orginX = e.getX();
                orginY = e.getY();
            }
            else if (e.isPrimaryButtonDown()) {
                isDrag = true;
                if (hitIdx < 0) {
                    return;
                }
                double endX = e.getX() + baseX - diffX;
                double endY = e.getY() + baseY - diffY;

                sprites.get(hitIdx).setX(endX);
                sprites.get(hitIdx).setY(endY);
            }
        });
        initBackGround(gc);
        Timeline timeline = new Timeline(new KeyFrame(Duration.millis(1000.0 / framesPerSecond), e -> {
            if (App.stopDraw)
                return;
            initBackGround(gc);
            drawConnection(gc);
            for (Sprite sprite: sprites) {
                sprite.draw(gc);
            }
            renderView();
        }));

        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();

    }
}
